/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
 */

package autodispose.lifecycle;

import io.reactivex.rxjava3.annotations.Nullable;
import io.reactivex.rxjava3.core.CompletableSource;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.BehaviorSubject;

/**
 * Test utility to create {@link LifecycleScopeProvider} instances for tests.
 *
 * <p>Supports start and stop lifecycle. Subscribing when outside of the lifecycle will throw
 * either {@link LifecycleNotStartedException} or {@link LifecycleEndedException}.
 */
public final class TestLifecycleScopeProvider implements
        LifecycleScopeProvider<TestLifecycleScopeProvider.TestLifecycle> {
    private final BehaviorSubject<TestLifecycle> lifecycleSubject;

    private TestLifecycleScopeProvider(@Nullable TestLifecycle initialValue) {
        if (initialValue == null) {
            lifecycleSubject = BehaviorSubject.create();
        } else {
            lifecycleSubject = BehaviorSubject.createDefault(initialValue);
        }
    }

    /**
     * Scope provider : create method
     *
     * @return new {@link TestLifecycleScopeProvider} instance.
     */
    public static TestLifecycleScopeProvider create() {
        return new TestLifecycleScopeProvider(null);
    }

    /**
     * Scope provider : createInitial method
     *
     * @param initialValue the initial lifecycle event to create the {@link
     *                     TestLifecycleScopeProvider} with.
     * @return new {@link TestLifecycleScopeProvider} instance with {@param initialValue} as its
     * initial lifecycle event.
     */
    public static TestLifecycleScopeProvider createInitial(TestLifecycle initialValue) {
        return new TestLifecycleScopeProvider(initialValue);
    }

    @Override
    public Observable<TestLifecycle> lifecycle() {
        return lifecycleSubject.hide();
    }

    @Override
    public CorrespondingEventsFunction<TestLifecycle> correspondingEvents() {
        return testLifecycle -> {
            switch (testLifecycle) {
                case STARTED:
                    return TestLifecycle.STOPPED;
                case STOPPED:
                    throw new LifecycleEndedException();
                default:
                    throw new IllegalStateException("Unknown lifecycle event.");
            }
        };
    }

    @Override
    public TestLifecycle peekLifecycle() {
        return lifecycleSubject.getValue();
    }

    @Override
    public CompletableSource requestScope() {
        return LifecycleScopes.resolveScopeFromLifecycle(this);
    }

    /**
     * Start the test lifecycle.
     */
    public void start() {
        lifecycleSubject.onNext(TestLifecycle.STARTED);
    }

    /**
     * Stop the test lifecycle.
     */
    public void stop() {
        if (lifecycleSubject.getValue() != TestLifecycle.STARTED) {
            throw new IllegalStateException("Attempting to stop lifecycle before starting it.");
        }
        lifecycleSubject.onNext(TestLifecycle.STOPPED);
    }

    /**
     * Test life cycle enum.
     */
    public enum TestLifecycle {
        /**
         * start enum
         */
        STARTED,
        /**
         * stop enum
         */
        STOPPED
    }
}
